﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Numerics;
using System.Security.Cryptography;
using System.Text;

namespace MainPaper
{
    static class Paper17
    {
        private const int LengthOfByteArray = 8;
        private static BigInteger p; // p = 2*q + 1
        private static BigInteger q ;
        private static BigInteger g ; 
        private static BigInteger[] xi;
        private static BigInteger[] yi;
        private static BigInteger[] ri;
        private static BigInteger[] si;
        private static BigInteger[] ci;
        private static BigInteger[] cki;
        private static BigInteger[,] ckiRecalculated;
        private static BigInteger[,] kij;
        private static BigInteger[,] kijRecalculated;
        private static BigInteger[,] dij;
        private static BigInteger[,] dijPrime;

        private static int n;
        private static MersenneTwister generator;
        private static SHA1 sha;
        private static bool _specialExecution;
        private static Dictionary<int, BigInteger> ps = new Dictionary<int, BigInteger>();
        private static Dictionary<int, BigInteger> gs = new Dictionary<int, BigInteger>();  

        public static long Execute(int numberOfParticipants, int bitlength, bool specialExecution)
        {
            _specialExecution = specialExecution;
            generator = new MersenneTwister();
            sha = new SHA1CryptoServiceProvider();
            n = numberOfParticipants;
            xi = new BigInteger[n];
            yi = new BigInteger[n];
            ri = new BigInteger[n];
            si = new BigInteger[n];
            ci = new BigInteger[n];
            cki = new BigInteger[n];
            ckiRecalculated = new BigInteger[n,n];
            kij = new BigInteger[n, n];
            kijRecalculated = new BigInteger[n, n];
            dij = new BigInteger[n, n];
            dijPrime = new BigInteger[n, n];
            
            ParameterGenerationPhase(bitlength);
            long time = 0;
            var sw = new Stopwatch();
            sw.Start();
            time += SecretDistribution();
            time += SubkeyComputation();
            time += KeyComputation();
            sw.Stop();
            return specialExecution? time : sw.ElapsedMilliseconds;
        }

        // 2.1
        static void ParameterGenerationPhase(int bitlength)
        {
            if (ps.ContainsKey(bitlength))
            {
                p = ps[bitlength];
                g = gs[bitlength];
                q = (p - 1) / 2;
            }
            else
            {
                p = AlgoAndMath.NextSafePrime(BigInteger.Pow(2, bitlength));
                //p = BigInteger.Parse("281474976711563");
                //p = BigInteger.Parse("340282366920938463463374607431768223907");
                q = (p - 1) / 2;
                g = AlgoAndMath.RandomBigInt(generator, p);
                while (BigInteger.ModPow(g, 2, p) == 1 || BigInteger.ModPow(g, q, p) == 1)
                {
                    g = AlgoAndMath.RandomBigInt(generator, p);
                }
                //g = BigInteger.Parse("86412248189911");
                //g = BigInteger.Parse("17891010277603318639");
                //now ord(g) = p, so we need to square it to make its order = q
                g = BigInteger.ModPow(g, 2, p);
                ps.Add(bitlength, p);
                gs.Add(bitlength, g);
            }
            
            
            for (int i = 0; i < n; i++)
            {
                xi[i] = AlgoAndMath.RandomBigInt(generator, q);
                yi[i] = BigInteger.ModPow(g, xi[i], p);
            }
        }

        //2.2
        static long SecretDistribution()
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < n; i++)
            {
                var ai = AlgoAndMath.RandomBigInt(generator, q); 
                for (int j = 0; j < n; j++)
                {
                    //if (i == j) continue;
                    kij[i, j] = BigInteger.ModPow(yi[j], ai, p);
                    kij[i, j] = kij[i, j] % q;
                }
                ci[i] = BigInteger.ModPow(g, ai, p);
                cki[i] = AlgoAndMath.RandomBigInt(generator, q); 
                for (int j = 0; j < n; j++)
                {
                    //if (i == j) continue;
                    dij[i, j] = (ci[i] * kij[i, j] + cki[i]) % q;
                    dijPrime[i, j] = dij[i, j] ^ kij[i, j];
                }
                var riSmall = AlgoAndMath.RandomBigInt(generator, q);
                ri[i] = BigInteger.ModPow(g, riSmall, p);
                var hCki = new BigInteger(sha.ComputeHash(cki[i].ToByteArray()));
                if (hCki.Sign == -1) hCki = BigInteger.Negate(hCki);
                si[i] = (xi[i]*hCki + riSmall*ri[i]) % q;
                if (_specialExecution) sw.Stop();
            }
            return sw.ElapsedMilliseconds;
        }

        //2.3
        static long SubkeyComputation()
        {
            // do not check time stamp
            Stopwatch sw = new Stopwatch();
            sw.Start();
            
            for (int i = 0; i < n; i++) // each participant perform several steps
            {
                for (int j = 0; j < n; j++) // for each received broadcast
                {
                    //step 2
                    //if (i == j) continue;
                    kijRecalculated[j, i] = BigInteger.ModPow(ci[j], xi[i], p) % q;
                    // for test purposes check that kij == kijRecalculated
                    //step 3
                    var dijRecalc = dijPrime[j, i] ^ kijRecalculated[j, i];
                    ckiRecalculated[i, j] = (dijRecalc - ci[j]*kijRecalculated[j, i]) % q;
                    if (ckiRecalculated[i, j] < 0) ckiRecalculated[i, j] += q; 
                    //step 4
                    //hash computation
                    var leftSide = BigInteger.ModPow(g, si[j], p);
                    var hCki = new BigInteger(sha.ComputeHash(ckiRecalculated[i, j].ToByteArray()));
                    if (hCki.Sign == -1) 
                        hCki = BigInteger.Negate(hCki);
                    var rightSide = (BigInteger.ModPow(yi[j], hCki, p) * BigInteger.ModPow(ri[j], ri[j], p)) % p; 
                    var res = BigInteger.Compare(leftSide, rightSide);
                }
                if (_specialExecution) sw.Stop();
            }
            return sw.ElapsedMilliseconds;
        }
        
        //2.5
        static long KeyComputation()
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < n; i++)
            {
                BigInteger sessionKey = 0;
                for (int j = 0; j < n; j++)
                {
                    sessionKey += ckiRecalculated[i, j];
                }
                sessionKey %= q;
                if (_specialExecution) sw.Stop();
            }
            return sw.ElapsedMilliseconds;
        }

        
    }
}
